//
// Created by tang on 2022/5/15.
//

#include "utils/date_utils.hpp"
#include <string>
#include <tuple>
#include <fmt/format.h>
#include <fmt/chrono.h>

std::string GetToday() {
    return fmt::format("{:%Y-%m-%d}", fmt::localtime(std::chrono::system_clock::now()));
}

std::string GetYesterday() {
    return fmt::format("{:%Y-%m-%d}", fmt::localtime(std::chrono::system_clock::now() - std::chrono::days(1)));
}

std::string GetTomorrow ()
{
    return fmt::format("{:%Y-%m-%d}", fmt::localtime(std::chrono::system_clock::now() + std::chrono::days(1)));
}

static struct tm FromYMD(int y, int m, int d)
{
    time_t raw_time;
    tm     stm{};
    time(&raw_time);
    stm = *localtime(&raw_time);
    stm.tm_year = y - 1900;
    stm.tm_mon = m - 1;
    stm.tm_mday = d;
    mktime(&stm);
    return stm;
}

static std::string ToString(const struct tm& tm)
{
    return fmt::format("{}-{:02d}-{:02d}", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
}

static time_t Tm2Time(struct tm tm)
{
    time_t time = mktime(&tm);
    return time;
}

static struct tm Time2Tm(time_t time)
{
    struct tm tm {};
#if _MSC_VER
    localtime_s(&tm, &time);
#else
    localtime_r(&time, &tm);
#endif
    return tm;
}

const char* ymd2week(const std::string& year, const std::string& month, const std::string& day) {
    static const char* week_names[]{
            "周日",
            "周一",
            "周二",
            "周三",
            "周四",
            "周五",
            "周六",
    };

    try {
        time_t raw_time;
        tm     stm{};
        time(&raw_time);
        stm         = *localtime(&raw_time);
        stm.tm_year = stoi(year) - 1900;
        stm.tm_mon = stoi(month) - 1;
        stm.tm_mday = stoi(day);
        mktime(&stm);

        return week_names[stm.tm_wday];
    }
    catch (...) {
        return "N/A";
    }
}

const char * date2week (const std::string &date)
{
    if (date.empty())
        return "N/A";

    const auto p1 = date.find('-');
    const auto p2 = date.find('-', p1 + 1);

    if (p1 == std::string::npos || p2 == std::string::npos)
        return "N/A";

    return ymd2week(date.substr(0, p1), date.substr(p1 + 1, p2 - p1), date.substr(p2 + 1));
}

static std::tuple<int,int> GetYearMonth(const std::string &date) {
    return std::make_tuple((int)std::stoi(date.substr(0,4)), (int)stoi(date.substr(5,2)));
}

static std::tuple<int, int, int> GetYearMonthDay(const std::string& date) {
    return std::make_tuple((int)std::stoi(date.substr(0, 4)), (int)stoi(date.substr(5, 2)), (int)stoi(date.substr(8, 2)));
}

static int IsLeap(int year)
{
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

static const char* month_last_days[2 * 13]{
    "0", "0", // padding
    "-31","-31",//1
    "-28","-29",//2
    "-31","-31",//3
    "-30","-30",//4
    "-31","-31",//5
    "-30","-30",//6
    "-31","-31",//7
    "-31","-31",//8
    "-30","-30",//9
    "-31","-31",//10
    "-30","-30",//11
    "-31","-31",//12
};

void DateComplition(const std::string &prefix, std::string &first_day, std::string &last_day)
{
    if (prefix.length() == 4) // full year
    {
        // just a year string
        first_day = prefix + "-01-01";
        last_day  = prefix + "-12-31";
    }
    else // month
    {
        auto [year,month] = GetYearMonth(prefix);
        int const is_leap = IsLeap(year);

        first_day = prefix + "-01";
        last_day  = prefix + month_last_days[2*month + is_leap];
    }
}

void AdjustDateRange(std::string& s, std::string& e, Granularity g, AdjustType adj)
{
    static const auto days = [](int n) -> int {
        return n * 24 * 60 * 60;
    };

    if (adj == AdjustType::Next) // 下一...
    {
        auto [y, m, d] = GetYearMonthDay(e);
        int q;
        struct tm tm;
        time_t time;
        switch (g)
        {
        case Granularity::Year:
            DateComplition(std::to_string(y + 1), s, e);
            break;
        case Granularity::Quarter:
            q = (m - 1) / 3;
            if (q == 3)
            {
                ++y;
                q = 0;
            }
            else
            {
                ++q;
            }
            m = q * 3 + 1; // 1
            s = std::format("{}-{:02d}-01", y, m);
            m += 2;
            e = std::format("{}-{:02d}{}", y, m, month_last_days[2 * m + IsLeap(y)]);
            break;
        case Granularity::Month:
            if (m == 12)
            {
                ++y;
                m = 1;
            }
            else
            {
                ++m;
            }
            DateComplition(fmt::format("{}-{:02d}", y, m), s, e);
            break;
        case Granularity::Week:
            tm = FromYMD(y, m, d);
            time = Tm2Time(tm);
            time += days(tm.tm_wday ? 8 - tm.tm_wday : 1); // start
            tm = Time2Tm(time);
            s = ToString(tm);

            time += days(6); // end
            tm = Time2Tm(time);
            e = ToString(tm);
            break;
        }
    }
    else
    {
        auto [y, m, d] = GetYearMonthDay(s);
        int q;
        struct tm tm;
        time_t time;
        switch (g)
        {
        case Granularity::Year:
            if (adj == AdjustType::Previous)
                --y;
            DateComplition(std::to_string(y), s, e);
            break;
        case Granularity::Quarter:
            q = (m - 1) / 3;
            if (adj == AdjustType::Previous)
            {
                if (q == 0)
                {
                    --y;
                    q = 3;
                }
                else
                    --q;
            }
            m = q * 3 + 1; // 1
            s = std::format("{}-{:02d}-01", y, m);
            m += 2;
            e = std::format("{}-{:02d}{}", y, m, month_last_days[2 * m + IsLeap(y)]);
            break;
        case Granularity::Month:
            if (adj == AdjustType::Previous)
            {
                if (m == 1)
                {
                    --y;
                    m = 12;
                }
                else
                    --m;
            }
            DateComplition(fmt::format("{}-{:02d}", y, m), s, e);
            break;
        case Granularity::Week:
            tm = FromYMD(y, m, d);
            time = Tm2Time(tm);
            if (adj == AdjustType::Previous) // start
                time -= days(tm.tm_wday ? 6 + tm.tm_wday : 13);
            else
                time -= days(tm.tm_wday ? tm.tm_wday - 1 : 7);
            tm = Time2Tm(time);
            s = ToString(tm);

            time += days(6); // end
            tm = Time2Tm(time);
            e = ToString(tm);

            break;
        }
    }
}
